package crawler;

import java.net.MalformedURLException;
import java.util.ArrayList;
import java.net.URL;
import java.util.LinkedHashSet;

/**
 * Downloader загружает страницы, собирает с них ссылки и хранит очередь
 * непосещенных ссылок, а так же очередь загруженных, но не обработанных страниц
 * 
 * @author Созинов Кирилл
 * @param thread поток, в котором выполняется данный downloader
 * @param runnedThreadsCount количество выполняемых в данный момент потоков
 * @param queue очередь гиперссылок на загрузку.
 * @param baseURL корневой URL сайта
 * @param downloaded очередь загруженных веб-страниц, готовых к обработке
 */

public class Downloader implements Runnable {
    
    private class HLink{
        public URL url;
        public Integer distance;
        
        /**
        * Класс, состоящий из URL и числа, характеризующего удаленность от корневой страницы
        * @param url объект типа URL - верифицировання гиперссылка
        * @param distance - удаленность страницы, на которую ведет url, от корневой страницы
        */ 
        public HLink(URL url, Integer distance){
            this.url=url;
            this.distance=distance;
        }
        
        @Override
        public String toString(){
            return url.toString();
        }
    }
    
    private Thread thread;
    private static Integer runnedThreadsCount = 0;
    private static volatile boolean stopped = false;
    private static String baseURL;
    private static LinkedHashSet<HLink> queue = new LinkedHashSet<HLink>();
    private static ArrayList<String> downloaded = new ArrayList<String>();

    
    /**
     * Конструктор по URL корневой страницы
     * @param baseURL URL корневой страницы сайта
     */
    Downloader(String baseURL){
        thread = new Thread(this);
        thread.setName("Downloader-"+runnedThreadsCount);
        this.baseURL = baseURL;
        addToQueue(baseURL,0);
        runnedThreadsCount++;
        System.out.println("Поток Downloader создан. Сейчас их создано: "+runnedThreadsCount);
    }
    
    /**
     * Конструктор для создания еще одного Downloader-а в дополнительном потоке
     * @throws IllegalStateException если данный конструктор был вызван до Downloader(String baseURL);
     */
    Downloader() throws IllegalStateException{
        if (baseURL==null) throw new IllegalStateException("Нельзя вызывать конструктор Downloader(), пока не выл вызван Downloader(String baseURL)");
        thread = new Thread(this);
        thread.setName("Downloader-"+runnedThreadsCount);
        runnedThreadsCount++;
        System.out.println("Поток Downloader.thread :"+thread.getName());
        System.out.println("Поток Downloader ("+Thread.currentThread().getName()+") создан. Сейчас их создано: "+runnedThreadsCount);
    }
   
    /**
     * Код, который начинает выполняться при вызове this.thread.start()<br>
     * Этот код выполняется в отдельном потоке.
     */
    @Override
    public void run(){
        System.out.println("Downloader.run работает в потоке: "+Thread.currentThread().getName());
        while (!stopped) {
            //Если очередь пуста, то отправляем поток в ожидание
            synchronized(queue) {
                if (queue.isEmpty()){
                    try {
                        queue.wait(5000);
                        System.out.println("Очередь на обработку пуста. Поток "+Thread.currentThread().getName()+" ожидает: вызова notify на объекте queue / попробует опросить очередь через пять секунд");
                    } catch (InterruptedException ex) {
                        System.err.println("Не удалось приостановить поток "+Thread.currentThread().getName());
                    }
                } else {
                    System.out.println(nextHref()+" обработана "+Thread.currentThread().getName());
                    synchronized(downloaded){
                        downloaded.add(baseURL);
                    }
                }
            }
        }
        runnedThreadsCount--;
        System.out.println("Поток Downloader ("+Thread.currentThread().getName()+") завершился. Осталось: "+runnedThreadsCount);
    }
    
    /**
     * Возвращает ссылку на поток, в котором выполняется данный downloader
     * @return Thread
     */    
    public Thread getThread(){
        return thread;
    }
    
    /**
     * Возвращает количество элементов в очереди на загрузку
     * @return число элементов, находящихся в очереди на загрузку
     */
    public static Integer getQueueSize(){
        return queue.size();
    }
    
    /**
     * !!!Этот код желательно вынести в анализатор как статический метод
     * Добавляет адрес веб-страницы в очередь на загрузку.
     * @param strLookLikeURI Адрес, который необходимо добавить в очередь
     * @param distance Удаленность данного URI от текущей страницы
     */
    public final boolean addToQueue(String strLookLikeURL, Integer distance){
        System.out.println("Пытаюсь добавить в очередь: "+strLookLikeURL+" в потоке "+Thread.currentThread().getName()+"\t");
        //попробуем создать URL из полученной строки
        try{
                HLink href = new HLink(new URL(strLookLikeURL),distance);
                System.out.println(strLookLikeURL+"  - является URL");
                synchronized (queue){
                    queue.add(href);
                }
                return true;
        }
        catch(MalformedURLException ex0){
            //если strLookLikeURI не URL, пытаемся приписать к нему базовый адрес
            try{
                System.out.println(strLookLikeURL+"  - не является URL. Пробуем добавить "+baseURL);
                HLink href = new HLink(new URL(baseURL+strLookLikeURL),distance);
                System.out.println(href.url + " успешно добавлено");
                synchronized (queue){
                    queue.add(href);
                }
                return true;
            }
            catch(MalformedURLException ex1){
                System.out.println(baseURL+strLookLikeURL + " не удалось добавить");
                //если baseURL+strLookLikeURI не URL, пытаемся добавляем спереди http://
                try{
                    System.out.println(baseURL+strLookLikeURL+"  - не является URL. Пробуем добавить http");
                    HLink href = new HLink(new URL("http://"+strLookLikeURL),distance);
                    System.out.println(href.url + " успешно добавлено");
                    synchronized (queue){
                        queue.add(href);
                    }
                    return true;
                }
                catch(MalformedURLException ex2){
                    System.out.println("http://"+strLookLikeURL + " не удалось добавить");
                    return false;
                }
            }
        }
    }
    
    /**
     * Забирает следующую гиперссылку из массива
     * @return HLink - гиперссылка на веб-страницу
     */
    private HLink nextHref() {
        synchronized (queue){
            HLink t = (HLink)queue.toArray()[0];
            queue.remove(t);
            return t;
        }
    }
    
    /**
     * Возвращает код наиболее давно загруженной страницы
     * или null, если страниц в очереди нет
     * @return код веб-страницы
     */
    public static String getDownloaded() {
        synchronized(downloaded){
            if(!downloaded.isEmpty()){
                String pageCode = downloaded.get(0);
                downloaded.remove(0);
                return pageCode;
            } else 
                return null;
                
        }
    }
    
    public static Integer getDownloadedSize(){
        synchronized(downloaded){
            return downloaded.size();
        }
    }

}
